/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Utility class for JAR FILE operations ( set of static methods )
 * 
 * @author Laurent GUERIN
 *  
 */
public class JarUtil
{
    public static final boolean             KEEP_PARENT_DIR   = true;

    public static final boolean             REMOVE_PARENT_DIR = false;

    // private static final TelosysClassLogger $log              = new TelosysClassLogger(JarUtil.class);

    //----------------------------------------------------------------------------
    /**
     * Private constructor 
     */
    private JarUtil()
    {
    }
    
    //----------------------------------------------------------------------------
    /**
     * Open a JAR file and return a JarFile instance
     * 
     * @param sJarFile
     * @return JarFile instance
     * @throws Exception
     */
    private static JarFile openJarFile(String sJarFile) throws Exception
    {
        JarFile jarFile = null;
        try
        {
            jarFile = new JarFile(sJarFile);
        } catch (IOException e)
        {
            throw new Exception("Cannot open JAR file '" + sJarFile + "'.", e);
        }
        return jarFile;
    }

    //----------------------------------------------------------------------------
    /**
     * Copy an input stream in an output stream
     * 
     * @param inStream
     * @param outStream
     * @throws IOException
     */
    private static void copyStream(InputStream inStream, OutputStream outStream) throws IOException
    {
        byte[] buffer = new byte[1024];
        int bytesRead;

        //--- Read input data and write it to the output stream
        while ((bytesRead = inStream.read(buffer)) != -1)
        {
            outStream.write(buffer, 0, bytesRead);
        }
    }

    //----------------------------------------------------------------------------
    /**
     * Extracts a file from a JAR file (replace the extracted file if it exists)
     * 
     * @param sJarFile
     *            jar file name
     * @param sJarEntry
     *            file name to extract
     * @param sExtractedFile
     *            file name of the extracted file
     * @throws Exception
     */
    public static void extract(String sJarFile, String sJarEntry, String sExtractedFile) throws Exception
    {
        JarFile jarFile = null;

        if ( null == sJarFile )
        {
            throw new IllegalArgumentException("Jar file name is null");
        }
        if ( null == sJarEntry )
        {
            throw new IllegalArgumentException("Jar entry name is null");
        }
        if ( null == sExtractedFile )
        {
            throw new IllegalArgumentException("Extracted file name is null");
        }

        try
        {
            jarFile = openJarFile(sJarFile);

            JarEntry jarEntry = jarFile.getJarEntry(sJarEntry);

            if (jarEntry != null)
            {
                extractEntry(jarFile, jarEntry, sExtractedFile);
            }
            else
            {
                throw new Exception("Entry '" + sJarEntry + "' not found in JAR file '" + sJarFile + "'.");
            }
        } finally
        {
            if (jarFile != null)
            {
                // $log.trace("Close jar file.");
                try
                {
                    jarFile.close();
                } catch (IOException e)
                {
                    // log and continue
                    //$log.error("Cannot close jar file ('" + sJarFile + "').");
                    throw new Exception("Cannot close jar file '" + sJarFile + "'.");
                }
            }
        }
    }

    //----------------------------------------------------------------------------
    /**
     * Extract the given entry from the given JAR file
     * The destination directories are created if necessary.
     * 
     * @param jarFile
     * @param jarEntry
     * @param sExtractedFile
     * @throws Exception
     */
    public static void extractEntry(JarFile jarFile, JarEntry jarEntry, String sExtractedFile) throws Exception
    {
        InputStream inputStream = null;
        FileOutputStream outputStream = null;

        if (jarFile == null)
        {
            throw new IllegalArgumentException("JarFile argument is null");
        }
        if (jarEntry == null)
        {
            throw new IllegalArgumentException("JarEntry argument is null");
        }
        if (jarEntry.isDirectory())
        {
            throw new IllegalArgumentException("JarEntry '" + jarEntry.getName() + "' is a directory");
        }
        if (sExtractedFile == null)
        {
            throw new IllegalArgumentException("Extracted file argument is null");
        }

        //--- Creates the directory of the file (if necessary)
        DirUtil.createFileDir(sExtractedFile);

        try
        {
            //--- Get an input stream for the entry.
            try
            {
                inputStream = jarFile.getInputStream(jarEntry);
            } catch (IOException e)
            {
                throw new Exception("Cannot get Input Stream for entry '" + jarEntry.getName() + "' not found in JAR file.");
            }

            //--- Create the output file (clobbering the file if it exists).
            try
            {
                outputStream = new FileOutputStream(sExtractedFile);
            } catch (FileNotFoundException e1)
            {
                throw new Exception("Cannot create output file '" + sExtractedFile + "'.");
            }

            //--- Create output file content (copy)
            try
            {
                copyStream(inputStream, outputStream);
            } catch (IOException e2)
            {
                throw new Exception("Cannot copy stream.", e2);
            }
        } finally
        {
            if (outputStream != null)
            {
                // $log.trace("Close outputStream.");
                try
                {
                    outputStream.close();
                } catch (IOException e)
                {
                    // log and continue
                    // $log.error("Cannot close output stream ('" + sExtractedFile + "').");
                    throw new Exception("Cannot close output stream ('" + sExtractedFile + "').");
                }
            }
            if (inputStream != null)
            {
                // $log.trace("Close inputStream.");
                try
                {
                    inputStream.close();
                } catch (IOException e)
                {
                    // log and continue
                    // $log.error("Cannot close input stream ( JarEntry = '" + jarEntry.getName() + "' ).");
                    throw new Exception("Cannot close input stream ( JarEntry = '" + jarEntry.getName() + "' ).");
                }
            }
        }
    }

    //----------------------------------------------------------------------------
    /**
     * Extract all the files corresponding to the given beginning
     * 
     * @param sJarFile
     *            the JAR file name
     * @param sBeginning
     *            the beginning of the file names to extract
     * @param sArgDestDir
     *            the destination directory
     * @param bKeepParentDir
     *            KEEP_PARENT_DIR (true) : to keep the parent directory of the JAR entry, <br>
     *            REMOVE_PARENT_DIR (false) : to remove the parent directory
     * @throws Exception
     */
    public static void extractFiles(String sJarFile, String sBeginning, String sArgDestDir, boolean bKeepParentDir) throws Exception
    {
        if (sJarFile == null)
        {
            throw new IllegalArgumentException("JAR file argument is null");
        }
        if (sBeginning == null)
        {
            throw new IllegalArgumentException("Beginning argument is null");
        }
        if (sArgDestDir == null)
        {
            throw new IllegalArgumentException("Destination directory argument is null");
        }
        JarFile jarFile = null;
        //--- Destination directory ( without "/" at the end )
        String sDestDir = sArgDestDir;
        if (sDestDir.endsWith("/"))
        {
            //--- Remove last char
            sDestDir = sDestDir.substring(0, sDestDir.length() - 1);
        }

        //        //--- Create the destination directory if necessary
        //        File destDir = new File(sDestDir);
        //        if ( ! destDir.exists() )
        //        {
        //            boolean bRet = destDir.mkdirs();
        //            if ( bRet != true )
        //            {
        //                throw new Exception("extractFiles() : Cannot creat destination directory '" + sDestDir + "'." );
        //            }
        //        }

        try
        {
            //--- Open the JAR file
            jarFile = new JarFile(sJarFile);

            //--- For each entry ...
            Enumeration<JarEntry> jarFileEntriesEnum = jarFile.entries();
            JarEntry currentJarEntry = null;
            while (jarFileEntriesEnum.hasMoreElements())
            {
                // get a JarEntry
                currentJarEntry = (JarEntry) jarFileEntriesEnum.nextElement();
                if (!currentJarEntry.isDirectory())
                {
                    String sEntryName = currentJarEntry.getName();
                    String sDestFile = null;

                    if (sEntryName.startsWith(sBeginning)) // This file is to extract
                    {
                        String sFileName = sEntryName;
                        if (bKeepParentDir == REMOVE_PARENT_DIR)
                        {
                            //--- Remove the parent directory
                            File f = new File(sEntryName);
                            sFileName = f.getName();
                        }

                        //--- Build the "Full Path" Destination File
                        if (sFileName.startsWith("/"))
                        {
                            sDestFile = sDestDir + sFileName;
                        }
                        else
                        {
                            sDestFile = sDestDir + "/" + sFileName;
                        }
                        //--- Extract the entry from the JAR file
                        // $log.trace(" . " + sEntryName + " --> " + sDestFile );
                        extractEntry(jarFile, currentJarEntry, sDestFile);
                    }
                }
            }
        } catch (IOException e)
        {
            throw new Exception("Cannot use JAR file '" + sJarFile + "'.", e);
        } finally
        {
            if (jarFile != null)
            {
                try
                {
                    jarFile.close();
                } catch (IOException e)
                {
                    // log and continue
                    //$log.error("Cannot close jar file ('" + sJarFile + "').");
                    throw new Exception("Cannot close jar file '" + sJarFile + "'.");
                }
            }
        }
    }

    //---------------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------------
    /**
     * Load properties from a properties file located in a jar file
     * @param sJarFileName the name of the 'jar'
     * @param sPropEntryName the name of the 'jar entry' used as the properties file
     * @return the properties
     * @throws Exception
     * @since 1.1.1
     */
    public static Properties loadProperties(String sJarFileName, String sPropEntryName ) throws Exception
    {
        if (sJarFileName == null)
        {
            throw new IllegalArgumentException("Jar file name is null");
        }
        if (sPropEntryName == null)
        {
            throw new IllegalArgumentException("Jar entry name is null");
        }
        
        Properties prop = null ;
        JarFile jarFile = null;
        try
        {
            jarFile = openJarFile(sJarFileName);

            JarEntry jarEntry = jarFile.getJarEntry(sPropEntryName);

            if (jarEntry != null)
            {
            	prop = loadProperties(jarFile, jarEntry);
            }
            else
            {
                throw new Exception("Entry '" + sPropEntryName + "' not found in JAR file '" + sJarFileName + "'.");
            }
        } finally
        {
            if (jarFile != null)
            {
                try
                {
                    jarFile.close();
                } catch (IOException e)
                {
                    throw new Exception("Cannot close jar file '" + sJarFileName + "'.");
                }
            }
        }
        return prop ;
    }
    
    //----------------------------------------------------------------------------
    /**
     * Load properties from a properties file located in a jar file
     * @param jarFile the 'jar' file object
     * @param jarEntry the the 'jar entry' object ( to be used as the properties file )
     * @return the properties
     * @throws Exception
     * @since 1.1.1
     */
    public static Properties loadProperties(JarFile jarFile, JarEntry jarEntry) throws Exception
    {
        if (jarFile == null)
        {
            throw new IllegalArgumentException("JarFile argument is null");
        }
        if (jarEntry == null)
        {
            throw new IllegalArgumentException("JarEntry argument is null");
        }
        if (jarEntry.isDirectory())
        {
            throw new IllegalArgumentException("JarEntry '" + jarEntry.getName() + "' is a directory");
        }

        Properties prop = new Properties();                
        InputStream inputStream = null;
        try
        {
            //--- Get an input stream for the entry.
            inputStream = jarFile.getInputStream(jarEntry);
            
            //--- Load the properties
            prop.load(inputStream);
        } 
        catch (IOException e)
        {
            throw new Exception("Cannot load properties from entry '" + jarEntry.getName() + "'.");
        } 
        finally
        {
            if (inputStream != null)
            {
                try
                {
                    inputStream.close();
                } catch (IOException e)
                {
                    throw new Exception("Cannot close input stream ( JarEntry = '" + jarEntry.getName() + "' ).");
                }
            }
        }
        return prop ;
    }
    
}